﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using BrainTechLLC;

namespace DragDropLibrary
{
    public partial class DragAndDrop
    {
        /// <summary>
        /// Tracks the source from which item(s) are being dragged
        /// </summary>
        public static ItemsControl DragSource;

        /// <summary>
        /// Stores/Tracks the underlying data type of the item(s) being dragged
        /// </summary>
        public static Type DragType;

        /// <summary>
        /// True indicates a drag operation is in progress, otherwise false 
        /// </summary>
        public static bool IsDragging;
        public static event EventHandler<ObjectEventArgs<ItemsControl, object>> BeginDrag;
        public static string DragSourceName;

        #region DragEnabled

        /// <summary>
        /// DragEnabled is an attached property that can be used to enable dragging elements from the specified items control
        /// Note that the drag source (which has DragEnabled="True") MUST derive from ItemsControl (i.e. ListBox, ListView, ItemsControl,
        /// ComboBox, many other WPF controls)
        /// </summary>
        public static readonly DependencyProperty DragEnabledProperty =
            DependencyProperty.RegisterAttached("DragEnabled", typeof(Boolean), typeof(DragAndDrop), new FrameworkPropertyMetadata(OnDragEnabledChanged));

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetDragEnabled(DependencyObject element, Boolean value)
        {
            element.SetValue(DragEnabledProperty, value);
        }

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static Boolean GetDragEnabled(DependencyObject element)
        {
            return (Boolean)element.GetValue(DragEnabledProperty);
        }

        /// <summary>
        /// This method is called whenever the attached property "DragEnabled" is changed or set
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="args"></param>
        public static void OnDragEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            Control ctrl = obj as Control;

            if (ctrl != null)
            {
                // attach or detach the preview mouse left button down event to initiate dragging
                if ((bool)args.NewValue == true)
                {
                    ctrl.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(ctrl_PreviewMouseLeftButtonDown);
                }
                else
                {
                    ctrl.PreviewMouseLeftButtonDown -= ctrl_PreviewMouseLeftButtonDown;
                }
            }
            else
            {
                throw new Exception("Dependency object " + obj.ToString() + " is not a UI Control");
            }
        }

        #endregion

        public static readonly DependencyProperty RegisterDragSourceNameProperty =
            DependencyProperty.RegisterAttached("RegisterDragSourceName", typeof(string), typeof(DragAndDrop), new FrameworkPropertyMetadata(string.Empty, OnRegisterDragSourceNameChanged));

        public static void SetRegisterDragSourceName(DependencyObject element, string value)
        {
            element.SetValue(RegisterDragSourceNameProperty, value);
        }

        public static string GetRegisterDragSourceName(DependencyObject element)
        {
            return (string)element.GetValue(RegisterDragSourceNameProperty);
        }

        public static void OnRegisterDragSourceNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null && !string.IsNullOrEmpty(args.NewValue.ToString()))
            {
                SetDragEnabled(obj, true);
            }
        }        
        
        public static readonly DependencyProperty DragDropEffectTypeProperty =
            DependencyProperty.RegisterAttached("DragDropEffectType", typeof(DragDropEffects), typeof(DragAndDrop), new FrameworkPropertyMetadata(DragDropEffects.Copy, OnDragDropEffectTypeChanged));

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetDragDropEffectType(DependencyObject element, DragDropEffects value)
        {
            element.SetValue(DragDropEffectTypeProperty, value);
        }

        /// <summary>
        /// Standard get/set methods for attached properties.  See Microsoft documentation on Attached Properties for more info
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static DragDropEffects GetDragDropEffectType(DependencyObject element)
        {
            return (DragDropEffects)element.GetValue(DragDropEffectTypeProperty);
        }

        public static void OnDragDropEffectTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            // Do nothing - this value will be requested when needed
            DragDropEffects newEffect = (DragDropEffects)args.NewValue;
        }

        #region Helper

        private static bool IsPermittedType(object o)
        {
            return !((o is Button) || (o is ToggleButton) || (o is TextBox));
        }

        /// <summary>
        /// Retrieve the object data from the drag origination point.  Use the ItemContainerGenerator to read
        /// the bound data item for the selected item control(s) in the ItemsControl-derived source
        /// </summary>
        /// <param name="source"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        private static object GetObjectDataFromPoint(ItemsControl source, Point point)
        {
            // what be under the mouse?
            UIElement element = source.InputHitTest(point) as UIElement;

            if (element != null && IsPermittedType(element))
            {
                //get the object from the element
                object data = DependencyProperty.UnsetValue;

                // trace up the visual tree until we find a valid data object
                while (data == DependencyProperty.UnsetValue)
                {
                    // try to get the object value for the corresponding element
                    data = source.ItemContainerGenerator.ItemFromContainer(element);

                    //get the parent and we will iterate again
                    if (data == DependencyProperty.UnsetValue)
                    {
                        // keep going up the tree to find a valid data object
                        element = VisualTreeHelper.GetParent(element) as UIElement;
                    }

                    // if we reach the actual ItemsControl then we must break to avoid an infinite loop
                    if (element == source || !IsPermittedType(element))
                    {
                        return null;
                    }
                }

                // Return the data that we fetched only if it is not Unset value, 
                // (Unset value indicates we did not find the data)
                if (data != DependencyProperty.UnsetValue)
                {
                    return data;
                }
            }

            return null;
        }

        #endregion

        public static void RaiseDropSuccessful(object droppedTo)
        {
            if (DropSuccessful != null)
            {
                DropSuccessful(droppedTo, new ObjectEventArgs<ItemsControl,object>(DragSource, droppedTo));
            }            
        }

        private static void ctrl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DragSource = sender as ItemsControl;
            DragSourceName = GetRegisterDragSourceName(DragSource);

            if (DragSource != null)
            {
                DragSource.MouseMove += new MouseEventHandler(DragSource_MouseMove);
            }
        }

        private static void DragSource_MouseMove(object sender, MouseEventArgs e)
        {
            DragSource.MouseMove -= DragSource_MouseMove;

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                object data = (object)GetObjectDataFromPoint(DragSource, e.GetPosition(DragSource));

                if (BeginDrag != null)
                {
                    BeginDrag(sender, new ObjectEventArgs<ItemsControl, object>(DragSource, data));
                }
                
                if (data != null)
                {                    
                    IsDragging = true;

                    // Only get type if it is a valid data object
                    DragType = data.GetType();
                    DragDrop.DoDragDrop(DragSource, data, GetDragDropEffectType(DragSource));
                }
            }
        }
    }
}
